namespace Smarts.Internal.Test.Core
{
    using NUnit.Framework;
    using Smarts.Core;
    using Smarts.Sequences;

    /// <summary>
    /// Tests for the Set class
    /// </summary>
    [TestFixture]
    public class SetTests
    {
        #region Set tests
        /// <summary>
        /// Tests that a parameterless constructor for Set
        /// will produce an empty set
        /// </summary>
        [Test]
        public void EmptySetCanBeCreated()
        {
            var emptySet = new Set<int>();
            Assert.That(emptySet.Count, Is.EqualTo(0));
        }

        /// <summary>
        /// Tests that values can be added to a set
        /// </summary>
        [Test]
        public void ValuesCanBeAddedToSet()
        {
            var mySet = new Set<int>();
            mySet[1] = true;
            Assert.That(mySet.Count, Is.EqualTo(1), "Number of items in set");
            Assert.That(mySet[1], "Item exists in set");
            Assert.That(mySet[2], Is.False, "Item does not exist in set");
            mySet[2] = true;
            Assert.That(mySet.Count, Is.EqualTo(2), "Number of items in set");
            Assert.That(mySet[2], "Item exists in set");
            mySet[2] = true;
            Assert.That(mySet.Count, Is.EqualTo(2), "Number of items in set");
            Assert.That(mySet[2], "Item exists in set");
        }

        /// <summary>
        /// Test that values can be removed from a set
        /// </summary>
        [Test]
        public void ValuesCanBeRemovedFromSet()
        {
            var mySet = new Set<int>();
            for (var i = 1; i <= 10; i++)
            {
                mySet[i] = true;
                Assert.That(mySet[i], "Item exists in set");
                Assert.That(mySet.Count, Is.EqualTo(i), "Number of items in set");
            }

            mySet[11] = false;
            Assert.That(mySet.Count, Is.EqualTo(10), "Number of items in set");
            mySet[5] = false;
            Assert.That(mySet.Count, Is.EqualTo(9), "Number of items in set");
            Assert.That(mySet[5], Is.False, "Item does not exist in set");
            mySet[5] = false;
            Assert.That(mySet.Count, Is.EqualTo(9), "Number of items in set");
            Assert.That(mySet[5], Is.False, "Item does not exist in set");
        }

        /// <summary>
        /// Test that the union of two sets contains all items
        /// from each of the two sets when the sets do not overlap
        /// </summary>
        [Test]
        public void UnionOfSetsSimple()
        {
            var setA = new Set<int>();
            var setB = new Set<int>();
            for (var i = 1; i <= 10; i++)
            {
                setA[i] = true;
                setB[i + 10] = true;
                Assert.That(setA[i], "Item exists in Set A");
                Assert.That(setB[i + 10], "Item exists in Set B");
                Assert.That(setA.Count, Is.EqualTo(i), "Number of items in Set A");
                Assert.That(setB.Count, Is.EqualTo(i), "Number of items in Set B");
            }

            var unionSet = setA + setB;
            Assert.That(unionSet.Count, Is.EqualTo(20), "Number of items in the Union set");
            for (var i = 1; i <= 20; i++)
            {
                Assert.That(unionSet[i], "Item exists in Union Set");
            }
        }

        /// <summary>
        /// Tests that when the union of two sets is calculated
        /// where the sets overlap that duplicates will not
        /// exist in the resultant set
        /// </summary>
        [Test]
        public void UnionOfSetsOverlap()
        {
            var setA = new Set<int>();
            var setB = new Set<int>();
            for (var i = 1; i <= 10; i++)
            {
                setA[i] = true;
                setB[i + 5] = true;
                Assert.That(setA[i], "Item exists in Set A");
                Assert.That(setB[i + 5], "Item exists in Set B");
                Assert.That(setA.Count, Is.EqualTo(i), "Number of items in Set A");
                Assert.That(setB.Count, Is.EqualTo(i), "Number of items in Set B");
            }

            var unionSet = setA + setB;
            Assert.That(unionSet.Count, Is.EqualTo(15), "Number of items in the Union set");
            for (var i = 1; i <= 15; i++)
            {
                Assert.That(unionSet[i], "Item exists in Union Set");
            }
        }

        /// <summary>
        /// Tests that the union of two equivalent sets is equal
        /// to the original set
        /// </summary>
        [Test]
        public void UnionOfSetsEqual()
        {
            var setA = new Set<int>();
            for (var i = 1; i <= 10; i++)
            {
                setA[i] = true;
                Assert.That(setA[i], "Item exists in Set A");
                Assert.That(setA.Count, Is.EqualTo(i), "Number of items in Set A");
            }

            var unionSet = setA + setA;
            Assert.That(unionSet.Count, Is.EqualTo(setA.Count), "Union set has same number of items as SetA");
            foreach (var i in setA)
            {
                Assert.That(unionSet[i], Is.True, "Union contains item from SetA");
            }
        }

        /// <summary>
        /// Tests that the union of a set with the empty set
        /// will be equivalent to the original set
        /// </summary>
        [Test]
        public void UnionOfSetsEmptySet()
        {
            var setA = new Set<int>();
            for (var i = 1; i <= 10; i++)
            {
                setA[i] = true;
                Assert.That(setA[i], "Item exists in Set A");
                Assert.That(setA.Count, Is.EqualTo(i), "Number of items in Set A");
            }

            Assert.That(setA + Set<int>.Empty, Is.EqualTo(setA), "Set unioned with the Empty set will equal the set");
            Assert.That(Set<int>.Empty + setA, Is.EqualTo(setA), "Set unioned with the Empty set will equal the set");
        }

        /// <summary>
        /// Tests that when two sets with no overlapping values
        /// are used in an interset operation, the resultant
        /// set will be empty
        /// </summary>
        [Test]
        public void IntersectionOfSetsSimple()
        {
            var setA = new Set<int>();
            var setB = new Set<int>();
            for (var i = 1; i <= 10; i++)
            {
                setA[i] = true;
                setB[i + 10] = true;
                Assert.That(setA[i], "Item exists in Set A");
                Assert.That(setB[i + 10], "Item exists in Set B");
                Assert.That(setA.Count, Is.EqualTo(i), "Number of items in Set A");
                Assert.That(setB.Count, Is.EqualTo(i), "Number of items in Set B");
            }

            var intersectSet = setA & setB;
            Assert.That(intersectSet.Count, Is.EqualTo(0), "Number of items in the Intersect set");
        }

        /// <summary>
        /// Tests that when two sets overlap the resulting
        /// intersect contains only those items in both sets
        /// </summary>
        [Test]
        public void IntersectionOfSetsOverlap()
        {
            var setA = new Set<int>();
            var setB = new Set<int>();
            for (var i = 1; i <= 10; i++)
            {
                setA[i] = true;
                setB[i + 5] = true;
                Assert.That(setA[i], "Item exists in Set A");
                Assert.That(setB[i + 5], "Item exists in Set B");
                Assert.That(setA.Count, Is.EqualTo(i), "Number of items in Set A");
                Assert.That(setB.Count, Is.EqualTo(i), "Number of items in Set B");
            }

            var intersectSet = setA & setB;
            Assert.That(intersectSet.Count, Is.EqualTo(5), "Number of items in the Intersect set");
            for (var i = 6; i <= 10; i++)
            {
                Assert.That(intersectSet[i], "Item exists in Intersect Set");
            }
        }

        /// <summary>
        /// Test that a set when intersected with an equivalent
        /// set will result in an equivalent set
        /// </summary>
        [Test]
        public void IntersectionOfSetsEqual()
        {
            var setA = new Set<int>();
            for (var i = 1; i <= 10; i++)
            {
                setA[i] = true;
                Assert.That(setA[i], "Item exists in Set A");
                Assert.That(setA.Count, Is.EqualTo(i), "Number of items in Set A");
            }

            var intersectSet = setA & setA;
            Assert.That(intersectSet.Count, Is.EqualTo(setA.Count), "Intersect set has same number of items as SetA");
            foreach (var i in setA)
            {
                Assert.That(intersectSet[i], Is.True, "Intersect contains item from SetA");
            }
        }

        /// <summary>
        /// Test that the intersection of any set with the
        /// empty set results in the empty set
        /// </summary>
        [Test]
        public void IntersectionOfSetsEmptySet()
        {
            var setA = new Set<int>();
            for (var i = 1; i <= 10; i++)
            {
                setA[i] = true;
                Assert.That(setA[i], "Item exists in Set A");
                Assert.That(setA.Count, Is.EqualTo(i), "Number of items in Set A");
            }

            Assert.That(setA & Set<int>.Empty, Is.EqualTo(Set<int>.Empty), "A set difference with an empty set will return the empty set");
            Assert.That(Set<int>.Empty & setA, Is.EqualTo(Set<int>.Empty), "A set difference with an empty set will return the empty set");
        }

        /// <summary>
        /// Test that basic set equality operations are
        /// able to be performed
        /// </summary>
        [Test]
        public void SetEquality()
        {
            var setA = new Set<int>();
            Assert.That(setA, Is.EqualTo(setA), "Set A is equal to itself (Equals)");
            var setB = new Set<int>();
            for (var i = 1; i <= 10; i++)
            {
                setA[i] = true;
                setB[i + 5] = true;
                Assert.That(setA[i], "Item exists in Set A");
                Assert.That(setB[i + 5], "Item exists in Set B");
                Assert.That(setA.Count, Is.EqualTo(i), "Number of items in Set A");
                Assert.That(setB.Count, Is.EqualTo(i), "Number of items in Set B");
            }

            Assert.That(setA != setB, Is.True, "Set A is not equal to Set B (!=)");
            Assert.That(setA, Is.Not.EqualTo(setB), "Set A is not equal to Set B (Equals)");
            Assert.That(setB == setA, Is.False, "Set A is not equal to Set B (==)");
            Assert.That(setB != setA, Is.True, "Set A is not equal to Set B (!=)");
            Assert.That(setA == setB, Is.False, "Set A is not equal to Set B (==)");
            var intersectAb = setA & setB;
            var intersectBa = setB & setA;
            Assert.That(intersectAb == intersectBa, Is.True, "Intersection of two sets is equal when parameters swapped");
            Assert.That(intersectBa == intersectAb, Is.True, "Intersection of two sets is equal when parameters swapped");
            Assert.That(intersectAb != intersectBa, Is.False, "Intersection of two sets is equal when parameters swapped");
            Assert.That(intersectBa != intersectAb, Is.False, "Intersection of two sets is equal when parameters swapped");
            var unionAb = setA + setB;
            var unionBa = setB + setA;
            Assert.That(unionAb == unionBa, Is.True, "Union of two sets is equal when parameters swapped");
            Assert.That(unionBa == unionAb, Is.True, "Union of two sets is equal when parameters swapped");
            Assert.That(unionAb != unionBa, Is.False, "Union of two sets is equal when parameters swapped");
            Assert.That(unionBa != unionAb, Is.False, "Union of two sets is equal when parameters swapped");
        }

        /// <summary>
        /// Test that set difference operations can be performed
        /// </summary>
        [Test]
        public void SetDifferenceSimple()
        {
            var setA = new Set<int>();
            var setB = new Set<int>();
            Assert.That(setA == Set<int>.Empty, "Set A is the Empty set");
            for (var i = 1; i <= 10; i++)
            {
                setA[i] = true;
                setB[i + 10] = true;
                Assert.That(setA[i], "Item exists in Set A");
                Assert.That(setB[i + 10], "Item exists in Set B");
                Assert.That(setA.Count, Is.EqualTo(i), "Number of items in Set A");
                Assert.That(setB.Count, Is.EqualTo(i), "Number of items in Set B");
            }

            var differenceAb = setA - setB;
            var differenceBa = setB - setA;
            Assert.That(differenceAb, Is.EqualTo(setA), "Difference is equal to Set A");
            Assert.That(differenceBa, Is.EqualTo(setB), "Difference is equal to Set B");
        }

        /// <summary>
        /// Test that set difference operations can be performed
        /// when the two sets overlap
        /// </summary>
        [Test]
        public void SetDifferenceOverlap()
        {
            var setA = new Set<int>();
            var setB = new Set<int>();
            for (var i = 1; i <= 10; i++)
            {
                setA[i] = true;
                setB[i + 5] = true;
                Assert.That(setA[i], "Item exists in Set A");
                Assert.That(setB[i + 5], "Item exists in Set B");
                Assert.That(setA.Count, Is.EqualTo(i), "Number of items in Set A");
                Assert.That(setB.Count, Is.EqualTo(i), "Number of items in Set B");
            }

            var differenceAb = setA - setB;
            var differenceBa = setB - setA;
            Assert.That(differenceAb, Is.Not.EqualTo(differenceBa), "Differences are not equal when sets overlap");
            
            Assert.That(differenceAb.Count, Is.EqualTo(5), "Number of items in the difference");
            Assert.That(differenceBa.Count, Is.EqualTo(5), "Number of items in the difference");
            for (var i = 1; i <= 5; i++)
            {
                Assert.That(differenceAb[i], "Item exists in difference");
            }

            for (var i = 11; i <= 15; i++)
            {
                Assert.That(differenceBa[i], "Items exist in difference");
            }

            var intersect = setA & setB;
            Assert.That(differenceAb + intersect == setA, "(SetA - SetB) + (SetA ^ SetB) == SetA");
            Assert.That(differenceBa + intersect == setB, "(SetB - SetA) + (SetA ^ SetB) == SetB");
            Assert.That(differenceAb + differenceBa + intersect == setA + setB, "Differences and intersects are equal to the Union of the two sets");
        }

        /// <summary>
        /// Tests that the set difference when two sets are equal
        /// is equivalent to the empty set
        /// </summary>
        [Test]
        public void SetDifferenceEqual()
        {
            var setA = new Set<int>();
            for (var i = 1; i <= 10; i++)
            {
                setA[i] = true;
                Assert.That(setA[i], "Item exists in Set A");
                Assert.That(setA.Count, Is.EqualTo(i), "Number of items in Set A");
            }

            var setB = new Set<int>(setA);
            var differenceAb = setA - setB;
            var differenceBa = setB - setA;
            Assert.That(differenceAb, Is.EqualTo(Set<int>.Empty), "Difference of a set and itself is the empty set");
            Assert.That(differenceBa, Is.EqualTo(Set<int>.Empty), "Difference of a set and itself is the empty set");
            var difference = Set<int>.Empty - setA;
            Assert.That(difference, Is.EqualTo(Set<int>.Empty), "Difference the empty set and some other set is the empty set");
        }

        /// <summary>
        /// Test that subtracting the empty set from any set will
        /// result in the original set
        /// </summary>
        [Test]
        public void SetDifferenceEmptySet()
        {
            var setA = new Set<int>();
            for (var i = 1; i <= 10; i++)
            {
                setA[i] = true;
                Assert.That(setA[i], "Item exists in Set A");
                Assert.That(setA.Count, Is.EqualTo(i), "Number of items in Set A");
            }

            Assert.That(setA - Set<int>.Empty, Is.EqualTo(setA), "Set minus the empty set is equal to the original set");
        }

        /// <summary>
        /// Test that subsets can be determined
        /// </summary>
        [Test]
        public void SetsAndSubsets()
        {
            var setA = new Set<int>(Integers.From(1).To(5));
            var setB = new Set<int>(Integers.From(6).To(10));
            Assert.That(setA.Contains(setB), Is.Not.True, "Set A does not contain Set B");
            Assert.That(setB.Contains(setA), Is.Not.True, "Set B does not contain Set A");
            Assert.That(setA.IsSubsetOf(setB), Is.Not.True, "Set A is not a subset of Set B");
            Assert.That(setB.IsSubsetOf(setA), Is.Not.True, "Set B is not a subset of Set A");
            setB = new Set<int>(Integers.From(3).To(10));
            Assert.That(setA.Contains(setB), Is.Not.True, "Set A does not contain Set B");
            Assert.That(setB.Contains(setA), Is.Not.True, "Set B does not contain Set A");
            Assert.That(setA.IsSubsetOf(setB), Is.Not.True, "Set A is not a subset of Set B");
            Assert.That(setB.IsSubsetOf(setA), Is.Not.True, "Set B is not a subset of Set A");
            setB = new Set<int>(Integers.From(1).To(10));
            Assert.That(setA.Contains(setB), Is.Not.True, "Set A does not contain Set B");
            Assert.That(setB.Contains(setA), Is.True, "Set B does contain Set A");
            Assert.That(setA.IsSubsetOf(setB), Is.True, "Set A is a subset of Set B");
            Assert.That(setB.IsSubsetOf(setA), Is.Not.True, "Set B is not a subset of Set A");

            Assert.That(setA.IsSubsetOf(setA), Is.True, "Set A is a subset of itself");
            Assert.That(setA.Contains(setA), Is.True, "Set A contains itself");
        }

        /// <summary>
        /// Test that intersection between sets can be determined
        /// </summary>
        [Test]
        public void SetIntersects()
        {
            var setA = new Set<int>(Integers.From(1).To(5));
            var setB = new Set<int>(Integers.From(6).To(10));
            Assert.That(setA.Intersects(setB), Is.Not.True, "Set A does not intersect Set B");
            Assert.That(setB.Intersects(setA), Is.Not.True, "Set B does not intersect Set A");
            setA = new Set<int>(Integers.From(1).To(9).By(2));
            setB = new Set<int>(Integers.From(2).To(10).By(2));
            Assert.That(setA.Intersects(setB), Is.Not.True, "Set A does not intersect Set B");
            Assert.That(setB.Intersects(setA), Is.Not.True, "Set B does not intersect Set A");
            Assert.That(setA.Intersects(Set<int>.Empty), Is.Not.True, "Set A does not intersect the Empty set");
            Assert.That(setB.Intersects(Set<int>.Empty), Is.Not.True, "Set B does not intersect the Empty set");
            Assert.That(setA.Intersects(setA), Is.True, "Set A intersects itself");
            Assert.That(setB.Intersects(setB), Is.True, "Set B intersects itself");
            var setC = setA + setB;
            Assert.That(setA.Intersects(setC), Is.True, "Set A intersects Set C");
            Assert.That(setC.Intersects(setA), Is.True, "Set C intersects Set A");
            Assert.That(setB.Intersects(setC), Is.True, "Set B intersects Set C");
            Assert.That(setC.Intersects(setB), Is.True, "Set C intersects Set B");
            setA = new Set<int>(Integers.From(1).To(5));
            setB = new Set<int>(Integers.From(5).To(10));
            Assert.That(setA.Intersects(setB), Is.True, "Set A intersects Set B");
            Assert.That(setB.Intersects(setA), Is.True, "Set B intersects Set A");
        }

        #endregion
    }
}